package net.jxtz.jxtzos.authentication;


import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import net.jxtz.jxtzos.entity.auth.User;
import net.jxtz.jxtzos.mapper.redis.RedisMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.UUID;

/**
 *
 * csrf令牌仓库
 *
 * @author a123
 */
@Slf4j
@Component
public class MyCsrfTokenRepository implements CsrfTokenRepository {

    /**
     * 参数名
     */
    public final static String CSRF_PARAM = "_csrf";

    /**
     * 请求头部名
     */
    public static final String CSRF_HEAD = "X-CSRF-TOKEN";

    /**
     * 用户名
     */
    private String userName;

    /**
     * 生成token
     *
     * @param httpServletRequest request
     * @return CsrfToken
     */
    @Override
    public CsrfToken generateToken(HttpServletRequest httpServletRequest) {
        log.info("开始生成csrf_token");
        final String token = UUID.randomUUID().toString().replaceAll("-","");
        return new DefaultCsrfToken(CSRF_HEAD, CSRF_PARAM, token);
    }

    // 保存token
    @Override
    public void saveToken(CsrfToken csrfToken, HttpServletRequest request, HttpServletResponse response) {
        log.info("存储csrf_token");
        if (Objects.isNull(csrfToken)){
            String s = "token不存在";
            redisMapper.deleteKey(userName + CSRF_PARAM);
            log.error(s);
            return;
        }

        final String token = csrfToken.getToken();

        redisMapper.set(userName + CSRF_PARAM, token);

        String url = "/authentication/login";
        if (request.getRequestURI().equals(url)) {
            // 登录端点的请求在登录成功后设置 JwtAuthenticationFilter
            return;
        }

        response.addHeader(CSRF_HEAD, csrfToken.getToken());
    }

    /**
     * 校验token，首先被调用
     * @param request HttpServletRequest
     * @return CsrfToken
     */
    @Override
    public CsrfToken loadToken(HttpServletRequest request) {
        log.info("开始校验csrf_token");
        try {
            final User user = JSONObject.parseObject(request.getInputStream(), StandardCharsets.UTF_8, User.class);
            if (!Objects.isNull(user)){
                this.userName = user.getUserName();
            }else {
                String header = request.getHeader(JwtUtils.TOKEN_HEAD).replaceFirst(JwtUtils.TOKEN_PREFIX,"");
                if (!"".equals(header)){
                    this.userName = JwtUtils.subject(header);
                }else {
                    throw new AuthenticationServiceException("token不存在");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (userName == null){
            throw new AuthenticationServiceException("用户信息不存在");
        }

        // 避免saveToken，如果直接返回null的话，会调用generateToken之后再接着调用saveToken
        // 如果是注册端点，给一个临时的csrf_token
        final String requestURI = request.getRequestURI();
        String url = "/authentication/register";
        if (url.equals(requestURI)){
            log.info("注册端点，跳转到生成token");
            return generateToken(request);
        }


        String token = (String) redisMapper.get(userName + CSRF_PARAM);
        String header = request.getHeader(CSRF_HEAD);
        log.warn("头部的csrf：" + header);
        if (token == null || !token.equals(header)){
            log.warn("不存在的csrf_token");
            return null;
        }

        log.info("csrf_token校验完毕");
        return new DefaultCsrfToken(CSRF_HEAD, CSRF_PARAM, token);
    }

    /**
     * redisMapper
     */
    private RedisMapper redisMapper;

    @Autowired
    public void setRedisMapper(RedisMapper redisMapper){
        this.redisMapper = redisMapper;
    }
}
